/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_model.cpp - Loading of various formats, also prepairs models for rendering
//


#include "r_local.h"


// The inline models from the current map are kept separate
static model_t				r_inlineModels[MAX_MODELS];

static model_t				*r_modelsHashTable[MODELS_HASH_SIZE];
static model_t				*r_models[MAX_MODELS];
static int					r_numModels;


/*
 ==============================================================================

    MODEL LIST

 ==============================================================================
*/


/*
 ==================
 R_AddMD2Model

 TODO: R_AddSubviewSurface
 FIXME: Should entity->origin really be the "center" for R_CullEntitySphere?
 FIXME2: Surface culling never gets hit
 ==================
*/
void R_AddMD2Model (renderEntity_t *entity)
{
	mdl_t			*model = entity->model->alias;
	mdlFrame_t		*curFrame, *oldFrame;
	mdlSurface_t	*surface;
	material_t		*material;
	vec3_t			mins, maxs;
	float			radius;
	int				cullBits, cullBitsSurf;
	int				i;

	if (entity->renderFX & RF_VIEWERMODEL)
	{
		if (!r_skipSuppress->integerValue && rg.renderViewParms.subview == SUBVIEW_NONE)
			return;
	}
	if (entity->renderFX & RF_WEAPONMODEL)
	{
		if (!r_skipSuppress->integerValue && rg.renderViewParms.subview != SUBVIEW_NONE)
			return;
	}

	if ((entity->frame < 0 || entity->frame >= model->numFrames) || (entity->oldFrame < 0 || entity->oldFrame >= model->numFrames))
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_AddMD2Model: no such frame %i to %i (%s)\n", entity->frame, entity->oldFrame, entity->model->realName);

		entity->frame = 0;
		entity->oldFrame = 0;
	}

	// Compute axially aligned mins and maxs
	curFrame = model->frames + entity->frame;
	oldFrame = model->frames + entity->oldFrame;

	if (curFrame == oldFrame)
	{
		VectorCopy(curFrame->mins, mins);
		VectorCopy(curFrame->maxs, maxs);

		radius = curFrame->radius;
	}
	else
	{
		VectorMin(curFrame->mins, oldFrame->mins, mins);
		VectorMax(curFrame->maxs, oldFrame->maxs, maxs);

		if (curFrame->radius > oldFrame->radius)
			radius = curFrame->radius;
		else
			radius = oldFrame->radius;
	}

	// Frustum cull
	if (entity->depthHack)
		cullBits = CULL_IN;
	else
	{
		cullBits = R_CullEntitySphere(entity, entity->origin, radius, rg.renderViewParms.planeBits);

		if (cullBits == CULL_OUT)
			return;

		if (cullBits != CULL_IN)
		{
			cullBits = R_CullEntityBox(entity, mins, maxs, cullBits);

			if (cullBits == CULL_OUT)
				return;
		}
	}

	// Mark as visible for this view
	entity->viewCount = rg.viewCount;

	rg.pc.entities++;

	// If it has a single surface, no need to cull again
	if (model->numSurfaces == 1)
		cullBits = CULL_IN;

	// Add all the surfaces
	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++)
	{
		// Frustum cull surfaces
		if (cullBits != CULL_IN)
		{
			cullBitsSurf = R_CullEntitySphere(entity, entity->origin, radius, cullBits);

			if (cullBitsSurf == CULL_OUT)
				continue;

			if (cullBitsSurf != CULL_IN)
			{
				if (R_CullEntityBox(entity, mins, maxs, cullBitsSurf) == CULL_OUT)
					continue;
			}
		}

		// Select material
		if (entity->customMaterial)
			material = entity->customMaterial;
		else
		{
			if (surface->numMaterials)
			{
				if (entity->skinIndex < 0 || entity->skinIndex >= surface->numMaterials)
				{
					Com_DevPrintf(S_COLOR_YELLOW "R_AddMD2Model: no such material %i (%s)\n", entity->skinIndex, entity->model->realName);

					entity->skinIndex = 0;
				}

				material = surface->materials[entity->skinIndex].material;
			}
			else
			{
				Com_DevPrintf(S_COLOR_YELLOW "R_AddMD2Model: no materials for surface (%s)\n", entity->model->realName);

				material = rg.defaultMaterial;
			}
		}

		if (!material->numStages)
			continue;

		// If it has a subview, add the surface
		if (material->subview != SUBVIEW_NONE)
		{
			if (!r_skipSuppress->integerValue && rg.renderViewParms.subview != SUBVIEW_NONE)
				continue;

			//R_AddSubviewSurface(material, entity, NULL);
		}

		// Add the surface
		R_AddMeshToList(MESH_ALIASMODEL, surface, material, entity);
	}
}


/*
 ==============================================================================

    BSP FORMAT LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadVertices
 ==================
*/
static void R_LoadVertices (const byte *data, const bspLump_t *lump)
{
	bspVertex_t   *in;
	vertex_t	  *out;
	int			  i;

	in = (bspVertex_t *)(data + lump->offset);
	if (lump->length % sizeof(bspVertex_t))
		Com_Error(ERR_DROP, "R_LoadVertices: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numVertices = lump->length / sizeof(bspVertex_t);
	rg.worldModel->vertices = out = (vertex_t *)Mem_Alloc(rg.worldModel->numVertices * sizeof(vertex_t), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numVertices * sizeof(vertex_t);

	for (i = 0; i < rg.worldModel->numVertices; i++, in++, out++)
	{
		out->point[0] = in->point[0];
		out->point[1] = in->point[1];
		out->point[2] = in->point[2];
	}
}

/*
 ==================
 R_LoadEdges
 ==================
*/
static void R_LoadEdges (const byte *data, const bspLump_t *lump)
{
	bspEdge_t   *in;
	edge_t	    *out;
	int 	    i;

	in = (bspEdge_t *)(data + lump->offset);
	if (lump->length % sizeof(bspEdge_t))
		Com_Error(ERR_DROP, "R_LoadEdges: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numEdges = lump->length / sizeof(bspEdge_t);
	rg.worldModel->edges = out = (edge_t *)Mem_Alloc(rg.worldModel->numEdges * sizeof(edge_t), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numEdges * sizeof(edge_t);

	for (i = 0; i < rg.worldModel->numEdges; i++, in++, out++)
	{
		out->v[0] = (unsigned short)in->v[0];
		out->v[1] = (unsigned short)in->v[1];
	}
}

/*
 ==================
 R_LoadSurfEdges
 ==================
*/
static void R_LoadSurfEdges (const byte *data, const bspLump_t *lump)
{
	int		*in, *out;
	int		i;
	
	in = (int *)(data + lump->offset);
	if (lump->length % sizeof(int))
		Com_Error(ERR_DROP, "R_LoadSurfEdges: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numSurfEdges = lump->length / sizeof(int);
	rg.worldModel->surfEdges = out = (int *)Mem_Alloc(rg.worldModel->numSurfEdges * sizeof(int), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numSurfEdges * sizeof(int);

	for (i = 0; i < rg.worldModel->numSurfEdges; i++)
		out[i] = in[i];
}

/*
 ==================
 R_LoadPlanes
 ==================
*/
static void R_LoadPlanes (const byte *data, const bspLump_t *lump)
{
	bspPlane_t   *in;
	cplane_t	 *out;
	int			 i;
	
	in = (bspPlane_t *)(data + lump->offset);
	if (lump->length % sizeof(bspPlane_t))
		Com_Error(ERR_DROP, "R_LoadPlanes: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numPlanes = lump->length / sizeof(bspPlane_t);
	rg.worldModel->planes = out = (cplane_t *)Mem_Alloc(rg.worldModel->numPlanes * sizeof(cplane_t), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numPlanes * sizeof(cplane_t);

	for (i = 0; i < rg.worldModel->numPlanes; i++, in++, out++)
	{
		out->normal[0] = in->normal[0];
		out->normal[1] = in->normal[1];
		out->normal[2] = in->normal[2];

		out->dist = in->dist;
		out->type = PlaneTypeForNormal(out->normal);
		SetPlaneSignbits(out);
	}
}

/*
 ==================
 R_GetTexSize
 ==================
*/
static void R_GetTexSize (texInfo_t *texInfo)
{
	material_t	   *material = texInfo->material;
	stage_t		   *stage;
	char		   name[MAX_PATH_LENGTH];
	mipTex_t	   mt;
	fileHandle_t   f;
	int			   i;

	// Look for a .wal texture first. This is so that retextures work.
	Str_SPrintf(name, sizeof(name), "%s.wal", material->name);
	FS_OpenFile(name, FS_READ, &f);
	if (f)
	{
		// Found it, use its dimensions
		FS_Read2(f, &mt, sizeof(mipTex_t));
		FS_CloseFile(f);

		texInfo->width = mt.width;
		texInfo->height = mt.height;
		return;
	}

	// No, so look for the first texture stage in the material
	for (i = 0, stage = material->stages; i < material->numStages; i++, stage++)
	{
		if (stage->shaders)
			continue;

		if (stage->textureStage.texture->flags & TF_INTERNAL)
			continue;

		// Found it, use its dimensions
		texInfo->width = stage->textureStage.texture->sourceWidth;
		texInfo->height = stage->textureStage.texture->sourceHeight;
		return;
	}

	// Couldn't find shit, so just default to 64x64
	texInfo->width = 64;
	texInfo->height = 64;
}

/*
 ==================
 R_CheckTexClamp
 ==================
*/
static void R_CheckTexClamp (texInfo_t *texInfo)
{
	material_t   *material = texInfo->material;
	stage_t		 *stage;
	int			 i;

	// If at least one stage uses any form of texture clamping, then all
	// surfaces using this material will need their texture coordinates
	// to be clamped to the 0.0 - 1.0 range for things to work properly
	for (i = 0, stage = material->stages; i < material->numStages; i++, stage++)
	{
		if (stage->shaders)
			continue;

		if (stage->textureStage.texture->isCubeMap)
			continue;

		if (stage->textureStage.texture->wrap != TW_REPEAT)
		{
			texInfo->clamp = true;
			return;
		}
	}

	// No need to clamp texture coordinates
	texInfo->clamp = false;
}

/*
 ==================
 R_LoadTexInfo
 ==================
*/
static void R_LoadTexInfo (const byte *data, const bspLump_t *lump)
{
	bspTexInfo_t   *in;
	texInfo_t	   *out, *step;
	int 			i, j;
	int				next;
	char			name[MAX_PATH_LENGTH];
	uint			surfaceParm;

	in = (bspTexInfo_t *)(data + lump->offset);
	if (lump->length % sizeof(bspTexInfo_t))
		Com_Error(ERR_DROP, "R_LoadTexInfo: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numTexInfo = lump->length / sizeof(bspTexInfo_t);
	rg.worldModel->texInfo = out = (texInfo_t *)Mem_Alloc(rg.worldModel->numTexInfo * sizeof(texInfo_t), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numTexInfo * sizeof(texInfo_t);

	for (i = 0; i < rg.worldModel->numTexInfo; i++, in++, out++)
	{
		out->flags = in->flags;

		for (j = 0; j < 4; j++)
		{
			out->vecs[0][j] = in->vecs[0][j];
			out->vecs[1][j] = in->vecs[1][j];
		}

		out->numFrames = 1;

		next = in->nextTexInfo;
		if (next > 0)
			out->next = rg.worldModel->texInfo + next;
		else
			out->next = NULL;

		// Special case for sky surfaces
		if (out->flags & SURF_SKY)
		{
			out->material = rg.worldModel->sky->material;
			out->width = 64;
			out->height = 64;
			out->clamp = false;

			continue;
		}

		// Special case for no-draw surfaces
		if (out->flags & SURF_NODRAW)
		{
			out->material = rg.noDrawMaterial;
			out->width = 64;
			out->height = 64;
			out->clamp = false;

			continue;
		}

		// Get surfaceParm
		if (out->flags & (SURF_WARP | SURF_TRANS33 | SURF_TRANS66))
		{
			surfaceParm = 0;

			if (out->flags & SURF_WARP)
				surfaceParm |= SURFACEPARM_WARP;
			if (out->flags & SURF_TRANS33)
				surfaceParm |= SURFACEPARM_TRANS33;
			if (out->flags & SURF_TRANS66)
				surfaceParm |= SURFACEPARM_TRANS66;
			if (out->flags & SURF_FLOWING)
				surfaceParm |= SURFACEPARM_FLOWING;
		}
		else
		{
			surfaceParm = SURFACEPARM_LIGHTING;

			if (out->flags & SURF_FLOWING)
				surfaceParm |= SURFACEPARM_FLOWING;
		}

		// Load the material
		Str_SPrintf(name, sizeof(name), "textures/%s", in->texture);
		out->material = R_FindMaterial(name, MT_GENERIC, surfaceParm);

		// Find texture dimensions
		R_GetTexSize(out);

		// Check if surfaces will need clamping
		R_CheckTexClamp(out);
	}

	// Count animation frames
	for (i = 0, out = rg.worldModel->texInfo; i < rg.worldModel->numTexInfo; i++, out++)
	{
		for (step = out->next; step && step != out; step = step->next)
			out->numFrames++;
	}
}

/*
 ==================
 R_CalcSurfaceOriginAndBounds

 Fills in surface origin and mins/maxs
 ==================
*/
static void R_CalcSurfaceOriginAndBounds (surface_t *surface)
{
	vertex_t   *v;
	int		   i, e;

	VectorClear(surface->origin);
	ClearBounds(surface->mins, surface->maxs);

	for (i = 0; i < surface->numEdges; i++)
	{
		e = rg.worldModel->surfEdges[surface->firstEdge + i];
		if (e >= 0)
			v = &rg.worldModel->vertices[rg.worldModel->edges[e].v[0]];
		else
			v = &rg.worldModel->vertices[rg.worldModel->edges[-e].v[1]];

		VectorAdd(surface->origin, v->point, surface->origin);
		AddPointToBounds(v->point, surface->mins, surface->maxs);
	}

	VectorScale(surface->origin, 1.0f / surface->numEdges, surface->origin);
}

/*
 ==================
 R_FindSurfaceTriangleWithEdge
 ==================
*/
static int R_FindSurfaceTriangleWithEdge (int numTriangles, surfTriangle_t *triangles, index_t start, index_t end, int ignore)
{
	surfTriangle_t   *triangle;
	int				 count, match;
	int				 i;

	count = 0;
	match = -1;

	for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++)
	{
		if ((triangle->index[0] == start && triangle->index[1] == end) || (triangle->index[1] == start && triangle->index[2] == end) || (triangle->index[2] == start && triangle->index[0] == end))
		{
			if (i != ignore)
				match = i;

			count++;
		}
		else if ((triangle->index[1] == start && triangle->index[0] == end) || (triangle->index[2] == start && triangle->index[1] == end) || (triangle->index[0] == start && triangle->index[2] == end))
			count++;
	}

	// Detect edges shared by three triangles and make them seams
	if (count > 2)
		match = -1;

	return match;
}

/*
 ==================
 R_BuildSurfaceTriangleNeighbors
 ==================
*/
static void R_BuildSurfaceTriangleNeighbors (int numTriangles, surfTriangle_t *triangles)
{
	surfTriangle_t   *triangle;
	int				 i;

	for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++)
	{
		triangle->neighbor[0] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, triangle->index[1], triangle->index[0], i);
		triangle->neighbor[1] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, triangle->index[2], triangle->index[1], i);
		triangle->neighbor[2] = R_FindSurfaceTriangleWithEdge(numTriangles, triangles, triangle->index[0], triangle->index[2], i);
	}
}

/*
 ==================
 R_BuildSurfacePolygon
 ==================
*/
static void R_BuildSurfacePolygon (surface_t *surface)
{
	surfTriangle_t   *triangle;
	surfVertex_t	 *vertex;
	texInfo_t		 *texInfo = surface->texInfo;
	vertex_t		 *v;
	int				 i, e;

	// Create triangles
	surface->numTriangles = (surface->numEdges - 2);
	surface->triangles = (surfTriangle_t *)Mem_Alloc(surface->numTriangles * sizeof(surfTriangle_t), rg.modelMemoryPool);

	rg.worldModel->size += surface->numTriangles * sizeof(surfTriangle_t);

	for (i = 2, triangle = surface->triangles; i < surface->numEdges; i++, triangle++)
	{
		triangle->index[0] = 0;
		triangle->index[1] = i-1;
		triangle->index[2] = i;
	}

	// Create vertices
	surface->numVertices = surface->numEdges;
	surface->vertices = (surfVertex_t *)Mem_Alloc(surface->numVertices * sizeof(surfVertex_t), rg.modelMemoryPool);

	rg.worldModel->size += surface->numVertices * sizeof(surfVertex_t);

	for (i = 0, vertex = surface->vertices; i < surface->numEdges; i++, vertex++)
	{
		// Vertex
		e = rg.worldModel->surfEdges[surface->firstEdge + i];
		if (e >= 0)
			v = &rg.worldModel->vertices[rg.worldModel->edges[e].v[0]];
		else
			v = &rg.worldModel->vertices[rg.worldModel->edges[-e].v[1]];

		VectorCopy(v->point, vertex->xyz);

		// Normal
		if (!(surface->flags & SURF_PLANEBACK))
			VectorCopy(surface->plane->normal, vertex->normal);
		else
			VectorNegate(surface->plane->normal, vertex->normal);

		// Tangents
		VectorNormalize2(texInfo->vecs[0], vertex->tangents[0]);
		VectorNormalize2(texInfo->vecs[1], vertex->tangents[1]);

		// Texture coordinates
		vertex->st[0] = (DotProduct(v->point, texInfo->vecs[0]) + texInfo->vecs[0][3]) / texInfo->width;
		vertex->st[1] = (DotProduct(v->point, texInfo->vecs[1]) + texInfo->vecs[1][3]) / texInfo->height;

		// Color
		if (texInfo->flags & SURF_TRANS33)
			MakeRGBA(vertex->color, 255, 255, 255, 255 * 0.33);
		else if (texInfo->flags & SURF_TRANS66)
			MakeRGBA(vertex->color, 255, 255, 255, 255 * 0.66);
		else
			MakeRGBA(vertex->color, 255, 255, 255, 255);
	}

	// Build triangle neighbors
	R_BuildSurfaceTriangleNeighbors(surface->numTriangles, surface->triangles);
}

/*
 ==================
 R_FixSurfaceTextureCoords
 ==================
*/
static void R_FixSurfaceTextureCoords (surface_t *surface)
{
	surfVertex_t   *vertex;
	vec2_t		   bias = {999999, 999999};
	float		   scale = 1.0f, max = 0.0f;
	int			   i;

	if (!surface->texInfo->clamp)
		return;

	// Find the coordinate bias for each axis, which corresponds to the
	// minimum coordinate values
	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++)
	{
		if (vertex->st[0] < bias[0])
			bias[0] = vertex->st[0];
		if (vertex->st[1] < bias[1])
			bias[1] = vertex->st[1];
	}

	// Bias so that both axes end up with a minimum coordinate of zero,
	// and find the maximum coordinate value for transforming them to
	// the 0.0 - 1.0 range
	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++)
	{
		vertex->st[0] -= bias[0];
		vertex->st[1] -= bias[1];

		if (vertex->st[0] > max)
			max = vertex->st[0];
		if (vertex->st[1] > max)
			max = vertex->st[1];
	}

	// The scale factor is the inverse of the maximum value
	if (max)
		scale = 1.0f / max;

	// Finally scale so that both axes end up with a maximum coordinate
	// of one
	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++)
	{
		vertex->st[0] *= scale;
		vertex->st[1] *= scale;
	}
}

/*
 ==================
 R_LoadFaces
 ==================
*/
static void R_LoadFaces (const byte *data, const bspLump_t *lump)
{
	bspFace_t	*in;
	surface_t   *out;
	int			i;

	in = (bspFace_t *)(data + lump->offset);
	if (lump->length % sizeof(bspFace_t))
		Com_Error(ERR_DROP, "R_LoadFaces: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numSurfaces = lump->length / sizeof(bspFace_t);
	rg.worldModel->surfaces = out = (surface_t *)Mem_Alloc(rg.worldModel->numSurfaces * sizeof(surface_t), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numSurfaces * sizeof(surface_t);

	for (i = 0; i < rg.worldModel->numSurfaces; i++, in++, out++)
	{
		out->flags = 0;
		out->firstEdge = in->firstEdge;
		out->numEdges = in->numEdges;

		if (LittleShort(in->side))
			out->flags |= SURF_PLANEBACK;

		out->plane = rg.worldModel->planes + in->planeNum;
		out->texInfo = rg.worldModel->texInfo + in->texInfo;

		out->viewCount = 0;
		out->worldCount = 0;
		out->lightCount = 0;
		out->fragmentCount = 0;

		// Find origin and bounds
		R_CalcSurfaceOriginAndBounds(out);

		// Create the polygon
		R_BuildSurfacePolygon(out);

		// Fix texture coordinates for clamping
		R_FixSurfaceTextureCoords(out);
	}
}

/*
 ==================
 R_LoadMarkSurfaces
 ==================
*/
static void R_LoadMarkSurfaces (const byte *data, const bspLump_t *lump)
{
	short		*in;
	surface_t   **out;
	int			i;
	
	in = (short *)(data + lump->offset);
	if (lump->length % sizeof(short))
		Com_Error(ERR_DROP, "R_LoadMarkSurfaces: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numMarkSurfaces = lump->length / sizeof(short);
	rg.worldModel->markSurfaces = out = (surface_t **)Mem_Alloc(rg.worldModel->numMarkSurfaces * sizeof(surface_t *), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numMarkSurfaces * sizeof(surface_t *);

	for (i = 0; i < rg.worldModel->numMarkSurfaces; i++)
		out[i] = rg.worldModel->surfaces + in[i];
}

/*
 ==================
 R_LoadVisibility
 ==================
*/
static void R_LoadVisibility (const byte *data, const bspLump_t *lump)
{

	if (!lump->length)
	{
		rg.worldModel->vis = NULL;
		return;
	}

	rg.worldModel->vis = (vis_t *)Mem_Alloc(lump->length, rg.modelMemoryPool);
	rg.worldModel->size += lump->length;

	memcpy(rg.worldModel->vis, data + lump->offset, lump->length);
}

/*
 ==================
 R_LoadLeafs
 ==================
*/
static void R_LoadLeafs (const byte *data, const bspLump_t *lump)
{
	bspLeaf_t   *in;
	leaf_t	    *out;
	int		    i, j;

	in = (bspLeaf_t *)(data + lump->offset);
	if (lump->length % sizeof(bspLeaf_t))
		Com_Error(ERR_DROP, "R_LoadLeafs: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numLeafs = lump->length / sizeof(bspLeaf_t);
	rg.worldModel->leafs = out = (leaf_t *)Mem_Alloc(rg.worldModel->numLeafs * sizeof(leaf_t), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numLeafs * sizeof(leaf_t);

	for (i = 0; i < rg.worldModel->numLeafs; i++, in++, out++)
	{
		out->contents = in->contents;
		out->viewCount = 0;
		out->visCount = 0;

		for (j = 0; j < 3; j++)
		{
			out->mins[j] = in->mins[j];
			out->maxs[j] = in->maxs[j];
		}

		out->parent = NULL;

		out->cluster = in->cluster;
		out->area = in->area;

		out->firstMarkSurface = rg.worldModel->markSurfaces + in->firstLeafFace;
		out->numMarkSurfaces = in->numLeafFaces;

		// Mark the surfaces for caustics
		if (out->contents & (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA))
		{
			for (j = 0; j < out->numMarkSurfaces; j++)
			{
				if (out->firstMarkSurface[j]->texInfo->flags & (SURF_WARP | SURF_TRANS33 | SURF_TRANS66))
					continue;

				if (out->firstMarkSurface[j]->texInfo->material->noOverlays)
					continue;

				if (out->contents & CONTENTS_WATER)
					out->firstMarkSurface[j]->flags |= SURF_IN_WATER;
				if (out->contents & CONTENTS_SLIME)
					out->firstMarkSurface[j]->flags |= SURF_IN_SLIME;
				if (out->contents & CONTENTS_LAVA)
					out->firstMarkSurface[j]->flags |= SURF_IN_LAVA;
			}
		}
	}
}

/*
 ==================
 R_SetParent
 ==================
*/
static void R_SetParent (node_t *node, node_t *parent)
{
	node->parent = parent;
	if (node->contents != -1)
		return;

	R_SetParent(node->children[0], node);
	R_SetParent(node->children[1], node);
}

/*
 ==================
 R_LoadNodes
 ==================
*/
static void R_LoadNodes (const byte *data, const bspLump_t *lump)
{
	bspNode_t   *in;
	node_t	    *out;
	int		    i, j, p;

	in = (bspNode_t *)(data + lump->offset);
	if (lump->length % sizeof(bspNode_t))
		Com_Error(ERR_DROP, "R_LoadNodes: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numNodes = lump->length / sizeof(bspNode_t);
	rg.worldModel->nodes = out = (node_t *)Mem_Alloc(rg.worldModel->numNodes * sizeof(node_t), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numNodes * sizeof(node_t);

	for (i = 0; i < rg.worldModel->numNodes; i++, in++, out++)
	{
		out->contents = -1;
		out->viewCount = 0;
		out->visCount = 0;

		for (j = 0; j < 3; j++)
		{
			out->mins[j] = in->mins[j];
			out->maxs[j] = in->maxs[j];
		}

		out->parent = NULL;

		out->plane = rg.worldModel->planes + in->planeNum;

		for (j = 0; j < 2; j++)
		{
			p = in->children[j];
			if (p >= 0)
				out->children[j] = rg.worldModel->nodes + p;
			else
				out->children[j] = (node_t *)(rg.worldModel->leafs + (-1 - p));
		}

		out->firstSurface = in->firstFace;
		out->numSurfaces = in->numFaces;
	}

	// Set nodes and leafs
	R_SetParent(rg.worldModel->nodes, NULL);
}

/*
 ==================
 R_SetupInlineModels
 ==================
*/
static void R_SetupInlineModels ()
{
	inlineModel_t   *inlineModel;
	model_t		    *model;
	int			    i;

	for (i = 0; i < rg.worldModel->numInlineModels; i++)
	{
		inlineModel = &rg.worldModel->inlineModels[i];
		model = &r_inlineModels[i];

		*model = *rg.worldModel;

		VectorCopy(inlineModel->mins, model->mins);
		VectorCopy(inlineModel->maxs, model->maxs);
		model->radius = inlineModel->radius;
		model->firstModelSurface = inlineModel->firstFace;
		model->numModelSurfaces = inlineModel->numFaces;

		if (i == 0)
			*rg.worldModel = *model;
	}
}

/*
 ==================
 R_LoadInlineModels
 ==================
*/
static void R_LoadInlineModels (const byte *data, const bspLump_t *lump)
{
	bspModel_t		*in;
	inlineModel_t   *out;
	int				i, j;

	in = (bspModel_t *)(data + lump->offset);
	if (lump->length % sizeof(bspModel_t))
		Com_Error(ERR_DROP, "R_LoadSubmodels: funny lump size in '%s'", rg.worldModel->realName);

	rg.worldModel->numInlineModels = lump->length / sizeof(dmodel_t);
	rg.worldModel->inlineModels = out = (inlineModel_t *)Mem_Alloc(rg.worldModel->numInlineModels * sizeof(inlineModel_t), rg.modelMemoryPool);
	rg.worldModel->size += rg.worldModel->numInlineModels * sizeof(inlineModel_t);

	for (i = 0; i < rg.worldModel->numInlineModels; i++, in++, out++)
	{
		for (j = 0; j < 3; j++)
		{
			out->origin[j] = in->origin[j];

			// Spread the mins / maxs by a pixel
			out->mins[j] = in->mins[j] - 1.0f;
			out->maxs[j] = in->maxs[j] + 1.0f;
		}

		out->radius = RadiusFromBounds(out->mins, out->maxs);

		out->firstFace = in->firstFace;
		out->numFaces = in->numFaces;
	}

	// Set up the inline models
	R_SetupInlineModels();
}

/*
 ==================
 R_LoadSky
 ==================
*/
static void R_LoadSky (const char *name, float rotate, const vec3_t axis)
{
	sky_t	*sky;

	rg.worldModel->sky = sky = (sky_t *)Mem_Alloc(sizeof(sky_t), rg.modelMemoryPool);
	rg.worldModel->size += sizeof(sky_t);

	sky->material = R_FindMaterial(name, MT_GENERIC, SURFACEPARM_SKY);

	sky->rotate = rotate;
	VectorCopy(axis, sky->axis);
}

/*
 ==================
 R_LoadWorldMap
 ==================
*/
void R_LoadWorldMap (const char *mapName, const char *skyName, float skyRotate, const vec3_t skyAxis)
{
	bspHeader_t   *header;
	byte		  *data;
	uint	      hashKey;

	if (rg.worldModel)
		Com_Error(ERR_DROP, "R_LoadWorldMap: attempted to redundantly load world map");

	// Load the file
	FS_ReadFile(mapName, (void **)&data);
	if (!data)
		Com_Error(ERR_DROP, "R_LoadWorldMap: '%s' not found", mapName);

	// Allocate a new model
	r_models[r_numModels++] = rg.worldModel = (model_t *)Mem_Alloc(sizeof(model_t), rg.modelMemoryPool);

	// Fill it in
	Com_StripExtension(mapName, rg.worldModel->name, sizeof(rg.worldModel->name));
	Str_Copy(rg.worldModel->realName, mapName, sizeof(rg.worldModel->realName));
	rg.worldModel->size = 0;
	rg.worldModel->type = MODEL_INLINE;

	// Sanity check
	header = (bspHeader_t *)data;

	if (header->id != BSP_ID)
		Com_Error(ERR_DROP, "R_LoadWorldMap: '%s' has wrong file id", rg.worldModel->realName);

	if (header->version != BSP_VERSION)
		Com_Error(ERR_DROP, "R_LoadWorldMap: '%s' has wrong version number (%i should be %i)", rg.worldModel->realName, header->version, BSP_VERSION);

	// Load lump data
	R_LoadSky(skyName, skyRotate, skyAxis);

	R_LoadVertices(data, &header->lumps[LUMP_VERTICES]);
	R_LoadEdges(data, &header->lumps[LUMP_EDGES]);
	R_LoadSurfEdges(data, &header->lumps[LUMP_SURFEDGES]);
	R_LoadPlanes(data, &header->lumps[LUMP_PLANES]);
	R_LoadTexInfo(data, &header->lumps[LUMP_TEXINFO]);
	R_LoadFaces(data, &header->lumps[LUMP_FACES]);
	R_LoadMarkSurfaces(data, &header->lumps[LUMP_LEAFFACES]);
	R_LoadVisibility(data, &header->lumps[LUMP_VISIBILITY]);
	R_LoadLeafs(data, &header->lumps[LUMP_LEAFS]);
	R_LoadNodes(data, &header->lumps[LUMP_NODES]);
	R_LoadInlineModels(data, &header->lumps[LUMP_MODELS]);

	// Free file data
	FS_FreeFile2(data);

	// Load external files
	R_LoadLights();

	// Set up some needed things
	rg.worldEntity->model = rg.worldModel;

	// Add to hash table
	hashKey = Str_HashKey(rg.worldModel->name, MODELS_HASH_SIZE, false);

	rg.worldModel->nextHash = r_modelsHashTable[hashKey];
	r_modelsHashTable[hashKey] = rg.worldModel;
}


/*
 ==============================================================================

    MD2 FORMAT LOADING

 ==============================================================================
*/


/*
 ==================
 R_CalcTangentVectors
 ==================
*/
static void R_CalcTangentVectors (int numTriangles, mdlTriangle_t *triangles, int numVertices, mdlXyzNormal_t *xyzNormals, mdlSt_t *st, int frameNum)
{
	mdlTriangle_t	 *triangle;
	mdlXyzNormal_t   *xyzNormal;
	float			 *pXyz[3], *pSt[3];
	vec5_t			 edge[2];
	vec3_t			 normal, tangents[2], cross;
	float			 d;
	int				 i, j;

	xyzNormals += numVertices * frameNum;

	// Clear
	for (i = 0, xyzNormal = xyzNormals; i < numVertices; i++, xyzNormal++)
	{
		VectorClear(xyzNormal->tangents[0]);
		VectorClear(xyzNormal->tangents[1]);
	}

	// Calculate normal and tangent vectors
	for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++)
	{
		pXyz[0] = (float *)(xyzNormals[triangle->index[0]].xyz);
		pXyz[1] = (float *)(xyzNormals[triangle->index[1]].xyz);
		pXyz[2] = (float *)(xyzNormals[triangle->index[2]].xyz);

		pSt[0] = (float *)(st[triangle->index[0]].st);
		pSt[1] = (float *)(st[triangle->index[1]].st);
		pSt[2] = (float *)(st[triangle->index[2]].st);

		// Find edges
		edge[0][0] = pXyz[1][0] - pXyz[0][0];
		edge[0][1] = pXyz[1][1] - pXyz[0][1];
		edge[0][2] = pXyz[1][2] - pXyz[0][2];
		edge[0][3] = pSt[1][0] - pSt[0][0];
		edge[0][4] = pSt[1][1] - pSt[0][1];

		edge[1][0] = pXyz[2][0] - pXyz[0][0];
		edge[1][1] = pXyz[2][1] - pXyz[0][1];
		edge[1][2] = pXyz[2][2] - pXyz[0][2];
		edge[1][3] = pSt[2][0] - pSt[0][0];
		edge[1][4] = pSt[2][1] - pSt[0][1];

		// Compute normal vector
		normal[0] = edge[1][1] * edge[0][2] - edge[1][2] * edge[0][1];
		normal[1] = edge[1][2] * edge[0][0] - edge[1][0] * edge[0][2];
		normal[2] = edge[1][0] * edge[0][1] - edge[1][1] * edge[0][0];

		VectorNormalize(normal);

		// Compute first tangent vector
		tangents[0][0] = edge[1][4] * edge[0][0] - edge[1][0] * edge[0][4];
		tangents[0][1] = edge[1][4] * edge[0][1] - edge[1][1] * edge[0][4];
		tangents[0][2] = edge[1][4] * edge[0][2] - edge[1][2] * edge[0][4];

		d = DotProduct(tangents[0], normal);
		VectorMA(tangents[0], -d, normal, tangents[0]);
		VectorNormalize(tangents[0]);

		// Compute second tangent vector
		tangents[1][0] = edge[1][0] * edge[0][3] - edge[1][3] * edge[0][0];
		tangents[1][1] = edge[1][1] * edge[0][3] - edge[1][3] * edge[0][1];
		tangents[1][2] = edge[1][2] * edge[0][3] - edge[1][3] * edge[0][2];

		d = DotProduct(tangents[1], normal);
		VectorMA(tangents[1], -d, normal, tangents[1]);
		VectorNormalize(tangents[1]);

		// Inverse tangent vectors if needed
		CrossProduct(tangents[1], tangents[0], cross);
		if (DotProduct(cross, normal) < 0)
		{
			VectorInverse(tangents[0]);
			VectorInverse(tangents[1]);
		}

		// Add the vectors
		for (j = 0; j < 3; j++)
		{
			VectorAdd(xyzNormals[triangle->index[j]].tangents[0], tangents[0], xyzNormals[triangle->index[j]].tangents[0]);
			VectorAdd(xyzNormals[triangle->index[j]].tangents[1], tangents[1], xyzNormals[triangle->index[j]].tangents[1]);
		}
	}

	// Renormalize
	for (i = 0, xyzNormal = xyzNormals; i < numVertices; i++, xyzNormal++)
	{
		VectorNormalize(xyzNormal->tangents[0]);
		VectorNormalize(xyzNormal->tangents[1]);
	}
}

/*
 ==================
 R_CalcFacePlanes
 ==================
*/
static void R_CalcFacePlanes (int numTriangles, mdlTriangle_t *triangles, mdlFacePlane_t *facePlanes, int numVertices, mdlXyzNormal_t *xyzNormals, int frameNum)
{
	mdlTriangle_t	*triangle;
	mdlFacePlane_t	*facePlane;
	float			*pXyz[3];
	vec3_t			edge[2];
	int				i;

	facePlanes += numTriangles * frameNum;
	xyzNormals += numVertices * frameNum;

	// Calculate face planes
	for (i = 0, triangle = triangles, facePlane = facePlanes; i < numTriangles; i++, triangle++, facePlane++)
	{
		pXyz[0] = (float *)(xyzNormals[triangle->index[0]].xyz);
		pXyz[1] = (float *)(xyzNormals[triangle->index[1]].xyz);
		pXyz[2] = (float *)(xyzNormals[triangle->index[2]].xyz);

		// Find edges
		VectorSubtract(pXyz[1], pXyz[0], edge[0]);
		VectorSubtract(pXyz[2], pXyz[0], edge[1]);

		// Compute normal
		CrossProduct(edge[1], edge[0], facePlane->normal);
		VectorNormalize(facePlane->normal);

		// Compute distance
		facePlane->dist = DotProduct(pXyz[0], facePlane->normal);
	}
}

/*
 ==================
 R_FindTriangleWithEdge
 ==================
*/
static int R_FindTriangleWithEdge (int numTriangles, mdlTriangle_t *triangles, index_t start, index_t end, int ignore)
{
	mdlTriangle_t	*triangle;
	int				count, match;
	int				i;

	count = 0;
	match = -1;

	for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++)
	{
		if ((triangle->index[0] == start && triangle->index[1] == end) || (triangle->index[1] == start && triangle->index[2] == end) || (triangle->index[2] == start && triangle->index[0] == end))
		{
			if (i != ignore)
				match = i;

			count++;
		}
		else if ((triangle->index[1] == start && triangle->index[0] == end) || (triangle->index[2] == start && triangle->index[1] == end) || (triangle->index[0] == start && triangle->index[2] == end))
			count++;
	}

	// Detect edges shared by three triangles and make them seams
	if (count > 2)
		match = -1;

	return match;
}

/*
 ==================
 R_BuildTriangleNeighbors
 ==================
*/
static void R_BuildTriangleNeighbors (int numTriangles, mdlTriangle_t *triangles)
{
	mdlTriangle_t   *triangle;
	int				i;

	for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++)
	{
		triangle->neighbor[0] = R_FindTriangleWithEdge(numTriangles, triangles, triangle->index[1], triangle->index[0], i);
		triangle->neighbor[1] = R_FindTriangleWithEdge(numTriangles, triangles, triangle->index[2], triangle->index[1], i);
		triangle->neighbor[2] = R_FindTriangleWithEdge(numTriangles, triangles, triangle->index[0], triangle->index[2], i);
	}
}

/*
 ==================
 R_LoadMD2
 ==================
*/
static bool R_LoadMD2 (model_t *model, const byte *data)
{
	md2Header_t		 *inModel;
	md2Frame_t		 *inFrame;
	md2Triangle_t	 *inTriangle;
	md2St_t			 *inSt;
	mdl_t			 *outModel;
	mdlFrame_t		 *outFrame;
	mdlSurface_t	 *outSurface;
	mdlMaterial_t	 *outMaterial;
	mdlTriangle_t	 *outTriangle;
	mdlSt_t			 *outSt;
	mdlXyzNormal_t   *outXyzNormal;
	int				 i, j;
	int				 ident, version;
	int				 indexRemap[MD2_MAX_TRIANGLES*3];
	index_t			 indexTable[MD2_MAX_TRIANGLES*3];
	index_t			 tmpXyzIndices[MD2_MAX_TRIANGLES*3], tmpStIndices[MD2_MAX_TRIANGLES*3];
	int				 numIndices;
	float			 skinWidth, skinHeight;
	vec3_t			 scale, translate;
	char			 name[MAX_OSPATH];

	inModel = (md2Header_t *)data;
	model->alias = outModel = (mdl_t*)Mem_Alloc(sizeof(mdl_t), rg.modelMemoryPool);
	
	model->size += sizeof(mdl_t);

	// Byte swap the header fields and sanity check
	ident = LittleLong(inModel->ident);
	if (ident != MD2_IDENT)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadMD2: wrong file id (%s)\n", model->realName);
		return false;
	}

	version = LittleLong(inModel->version);
	if (version != MD2_VERSION)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadMD2: wrong version number (%i should %i) (%s)\n", version, MD2_VERSION, model->realName);
		return false;
	}

	outModel->numFrames = LittleLong(inModel->numFrames);
	if (outModel->numFrames > MD2_MAX_FRAMES || outModel->numFrames <= 0)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadMD2: invalid number of frames (%i) (%s)\n", outModel->numFrames, model->realName);
		return false;
	}

	outModel->numTags = 0;
	outModel->numSurfaces = 1;

	skinWidth = (float)LittleLong(inModel->skinWidth);
	if (skinWidth <= 0)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadMD2: invalid skin width (%i) (%s)\n", (int)skinWidth, model->realName);
		return false;
	}

	skinHeight = (float)LittleLong(inModel->skinHeight);
	if (skinHeight <= 0)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadMD2: invalid skin height (%i) (%s)\n", (int)skinHeight, model->realName);
		return false;
	}

	skinWidth = 1.0 / skinWidth;
	skinHeight = 1.0 / skinHeight;

	outModel->surfaces = outSurface = (mdlSurface_t*)Mem_Alloc(sizeof(mdlSurface_t), rg.modelMemoryPool);

	model->size += sizeof(mdlSurface_t);

	outSurface->numTriangles = LittleLong(inModel->numTris);
	if (outSurface->numTriangles > MD2_MAX_TRIANGLES || outSurface->numTriangles <= 0)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadMD2: invalid number of triangles (%i) (%s)\n", outSurface->numTriangles, model->realName);
		return false;
	}

	outSurface->numVertices = LittleLong(inModel->numXyz);
	if (outSurface->numVertices > MD2_MAX_VERTS || outSurface->numVertices <= 0)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadMD2: invalid number of vertices (%i) (%s)\n", outSurface->numVertices, model->realName);
		return false;
	}
	
	outSurface->numMaterials = LittleLong(inModel->numSkins);
	if (outSurface->numMaterials > MD2_MAX_SKINS || outSurface->numMaterials < 0)
	{
		Com_DevPrintf(S_COLOR_YELLOW "R_LoadMD2: invalid number of skins (%i) (%s)\n", outSurface->numMaterials, model->realName);
		return false;
	}

	// Load triangle lists
	inTriangle = (md2Triangle_t *)((byte *)inModel + LittleLong(inModel->ofsTris));

	for (i = 0; i < outSurface->numTriangles; i++)
	{
		tmpXyzIndices[i*3+0] = (index_t)LittleShort(inTriangle[i].indexXyz[0]);
		tmpXyzIndices[i*3+1] = (index_t)LittleShort(inTriangle[i].indexXyz[1]);
		tmpXyzIndices[i*3+2] = (index_t)LittleShort(inTriangle[i].indexXyz[2]);

		tmpStIndices[i*3+0] = (index_t)LittleShort(inTriangle[i].indexSt[0]);
		tmpStIndices[i*3+1] = (index_t)LittleShort(inTriangle[i].indexSt[1]);
		tmpStIndices[i*3+2] = (index_t)LittleShort(inTriangle[i].indexSt[2]);
	}

	// Build list of unique vertices
	outSurface->numVertices = 0;

	numIndices = outSurface->numTriangles * 3;
	memset(indexRemap, -1, MD2_MAX_TRIANGLES * 3 * sizeof(int));

	for (i = 0; i < numIndices; i++)
	{
		if (indexRemap[i] != -1)
			continue;

		// Remap duplicates
		for (j = i + 1; j < numIndices; j++)
		{
			if ((tmpXyzIndices[j] == tmpXyzIndices[i]) && (tmpStIndices[j] == tmpStIndices[i]))
			{
				indexRemap[j] = i;
				indexTable[j] = outSurface->numVertices;
			}
		}

		// Add unique vertex
		indexRemap[i] = i;
		indexTable[i] = outSurface->numVertices++;
	}

	// Copy triangle lists
	outSurface->triangles = outTriangle = (mdlTriangle_t*)Mem_Alloc(outSurface->numTriangles * sizeof(mdlTriangle_t), rg.modelMemoryPool);

	model->size += outSurface->numTriangles * sizeof(mdlTriangle_t);

	for (i = 0; i < numIndices; i += 3, outTriangle++)
	{
		outTriangle->index[0] = indexTable[i+0];
		outTriangle->index[1] = indexTable[i+1];
		outTriangle->index[2] = indexTable[i+2];
	}

	// Load base S and T vertices
	inSt = (md2St_t *)((byte *)inModel + LittleLong(inModel->ofsSt));
	outSurface->st = outSt = (mdlSt_t*)Mem_Alloc(outSurface->numVertices * sizeof(mdlSt_t), rg.modelMemoryPool);

	model->size += outSurface->numVertices * sizeof(mdlSt_t);

	for (i = 0; i < numIndices; i++)
	{
		if (indexRemap[i] != i)
			continue;

		outSt[indexTable[i]].st[0] = ((float)LittleShort(inSt[tmpStIndices[i]].s) + 0.5) * skinWidth;
		outSt[indexTable[i]].st[1] = ((float)LittleShort(inSt[tmpStIndices[i]].t) + 0.5) * skinHeight;
	}

	// Allocate space for face planes
	outSurface->facePlanes = (mdlFacePlane_t*)Mem_Alloc(outModel->numFrames * outSurface->numTriangles * sizeof(mdlFacePlane_t), rg.modelMemoryPool);
	model->size += outModel->numFrames * outSurface->numTriangles * sizeof(mdlFacePlane_t);

	// Load the frames
	outModel->frames = outFrame = (mdlFrame_t*)Mem_Alloc(outModel->numFrames * sizeof(mdlFrame_t), rg.modelMemoryPool);
	outSurface->xyzNormals = outXyzNormal = (mdlXyzNormal_t*)Mem_Alloc(outModel->numFrames * outSurface->numVertices * sizeof(mdlXyzNormal_t), rg.modelMemoryPool);

	model->size += outModel->numFrames * sizeof(mdlFrame_t);
	model->size += outModel->numFrames * outSurface->numVertices * sizeof(mdlXyzNormal_t);

	for (i = 0; i < outModel->numFrames; i++, outFrame++, outXyzNormal += outSurface->numVertices)
	{
		inFrame = (md2Frame_t *)((byte *)inModel + LittleLong(inModel->ofsFrames) + i*LittleLong(inModel->frameSize));

		scale[0] = LittleFloat(inFrame->scale[0]);
		scale[1] = LittleFloat(inFrame->scale[1]);
		scale[2] = LittleFloat(inFrame->scale[2]);

		translate[0] = LittleFloat(inFrame->translate[0]);
		translate[1] = LittleFloat(inFrame->translate[1]);
		translate[2] = LittleFloat(inFrame->translate[2]);

		VectorCopy(translate, outFrame->mins);
		VectorMA(translate, 255.0, scale, outFrame->maxs);

		outFrame->radius = RadiusFromBounds(outFrame->mins, outFrame->maxs);

		// Load XYZ vertices
		for (j = 0; j < numIndices; j++)
		{
			if (indexRemap[j] != j)
				continue;

			outXyzNormal[indexTable[j]].xyz[0] = (float)inFrame->verts[tmpXyzIndices[j]].v[0] * scale[0] + translate[0];
			outXyzNormal[indexTable[j]].xyz[1] = (float)inFrame->verts[tmpXyzIndices[j]].v[1] * scale[1] + translate[1];
			outXyzNormal[indexTable[j]].xyz[2] = (float)inFrame->verts[tmpXyzIndices[j]].v[2] * scale[2] + translate[2];

			ByteToDir(inFrame->verts[tmpXyzIndices[j]].lightNormalIndex, outXyzNormal[indexTable[j]].normal);
		}

		// Calculate tangent vectors
		R_CalcTangentVectors(outSurface->numTriangles, outSurface->triangles, outSurface->numVertices, outSurface->xyzNormals, outSurface->st, i);

		// Calculate face planes
		R_CalcFacePlanes(outSurface->numTriangles, outSurface->triangles, outSurface->facePlanes, outSurface->numVertices, outSurface->xyzNormals, i);
	}

	// Build triangle neighbors
	R_BuildTriangleNeighbors(outSurface->numTriangles, outSurface->triangles);

	// Register all skins
	outSurface->materials = outMaterial = (mdlMaterial_t*)Mem_Alloc(outSurface->numMaterials * sizeof(mdlMaterial_t), rg.modelMemoryPool);

	model->size += outSurface->numMaterials * sizeof(mdlMaterial_t);

	for (i = 0; i < outSurface->numMaterials; i++, outMaterial++)
	{
		Com_StripExtension((char *)inModel + LittleLong(inModel->ofsSkins) + i*MAX_QPATH, name, sizeof(name));
		outMaterial->material = R_FindMaterial(name, MT_GENERIC, SURFACEPARM_LIGHTING);
	}

	return true;
}


/*
 ==============================================================================

    MODEL REGISTRATION AND CREATION

 ==============================================================================
*/


/*
 ==================
 R_LoadModel
 ==================
*/
static model_t *R_LoadModel (const char *name, byte *data, modelType_t type)
{
	model_t	*model;
	uint	hashKey;

	// Allocate a new model
	if (r_numModels == MAX_MODELS)
		Com_Error(ERR_DROP, "R_LoadModel: MAX_MODELS hit");

	r_models[r_numModels++] = model = (model_t *)Mem_Alloc(sizeof(model_t), rg.modelMemoryPool);

	// Fill it in
	Com_StripExtension(name, model->name, sizeof(model->name));
	Str_Copy(model->realName, name, sizeof(model->realName));
	model->size = 0;
	model->type = type;

	// Call the appropriate loader
	switch (type){
	case MODEL_MD2:
		if (!R_LoadMD2(model, data))
			model->type = MODEL_BAD;

		break;
	case MODEL_BAD:

		break;
	}

	// Free model data
	if (data != NULL)
		FS_FreeFile2(data);

	// Add to hash table
	hashKey = Str_HashKey(model->name, MODELS_HASH_SIZE, false);

	model->nextHash = r_modelsHashTable[hashKey];
	r_modelsHashTable[hashKey] = model;

	return model;
}

/*
 ==================
 R_FindModel
 ==================
*/
model_t *R_FindModel (const char *name)
{
	model_t	*model;
	byte	*data;
	char	checkName[MAX_OSPATH], loadName[MAX_OSPATH];
	int		i;
	uint	hashKey;

	if (!name || !name[0])
		Com_Error(ERR_DROP, "R_FindModel: NULL model name");

	if (Str_Length(name) >= MAX_PATH_LENGTH)
		Com_Error(ERR_DROP, "R_FindModel: model name exceeds MAX_PATH_LENGTH");

	// Inline models are grabbed only from worldModel
	if (name[0] == '*')
	{
		i = Str_ToInteger(name+1);
		if (!rg.worldModel || (i < 1 || i >= rg.worldModel->numInlineModels))
			Com_Error(ERR_DROP, "R_FindModel: bad inline model number (%i)", i);

		return &r_inlineModels[i];
	}

	// Strip extension
	Com_StripExtension(name, checkName, sizeof(checkName));

	// Check if it already exists
	hashKey = Str_HashKey(checkName, MODELS_HASH_SIZE, false);

	for (model = r_modelsHashTable[hashKey]; model; model = model->nextHash)
	{
		if (!Str_ICompare(model->name, checkName))
			return model;
	}

	// Try load a MD2 file
	Str_SPrintf(loadName, sizeof(loadName), "%s.md2", checkName);
	FS_ReadFile(loadName, (void **)&data);
	if (data)
		return R_LoadModel(loadName, data, MODEL_MD2);

	// Not found
	Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find model '%s'\n", name);

	return R_LoadModel(name, NULL, MODEL_BAD);
}

/*
 ==================
 R_RegisterModel
 ==================
*/
model_t *R_RegisterModel (const char *name)
{
	return R_FindModel(name);
}


/*
 ==============================================================================

    CONSOLE COMMANDS

 ==============================================================================
*/


void R_ListModels_f ()
{
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 R_InitModels
 ==================
*/
void R_InitModels ()
{
	// First entity is the world
	rg.worldEntity = &rg.scene.entities[0];
	memset(rg.worldEntity, 0, sizeof(renderEntity_t));

	rg.worldEntity->reType = RE_MODEL;
	rg.worldEntity->index = 0;
	rg.worldEntity->model = rg.worldModel;

	VectorClear(rg.worldEntity->origin);
	AxisClear(rg.worldEntity->axis);

	rg.worldEntity->customMaterial = NULL;

	rg.worldEntity->materialParms[MATERIALPARM_RED] = 1.0f;
	rg.worldEntity->materialParms[MATERIALPARM_GREEN] = 1.0f;
	rg.worldEntity->materialParms[MATERIALPARM_BLUE] = 1.0f;
	rg.worldEntity->materialParms[MATERIALPARM_ALPHA] = 1.0f;
	rg.worldEntity->materialParms[MATERIALPARM_TIME_OFFSET] = 0.0f;
	rg.worldEntity->materialParms[MATERIALPARM_DIVERSITY] = 0.0f;
	rg.worldEntity->materialParms[MATERIALPARM_GENERAL] = 0.0f;
	rg.worldEntity->materialParms[MATERIALPARM_MODE] = 1.0f;
}

/*
 ==================
 R_ShutdownModels
 ==================
*/
void R_ShutdownModels ()
{
	model_t	*model;
	int		i;

	for (i = 0; i < r_numModels; i++)
	{
		model = r_models[i];

		Mem_Free(model);
	}

	memset(r_modelsHashTable, 0, sizeof(r_modelsHashTable));
	memset(r_models, 0, sizeof(r_models));

	r_numModels = 0;
}